package com.egzosn.infrastructure.database.jdbc;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.*;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;

/**
 * Generic callback interface for code that operates on a PreparedStatement.
 * Allows to execute any number of operations on a single PreparedStatement,
 * for example a single {@code executeUpdate} call or repeated
 * {@code executeUpdate} calls with varying parameters.
 *
 * <p>Used internally by JdbcTemplate, but also useful for application code.
 * Note that the passed-in PreparedStatement can have been created by the
 * framework or by a custom PreparedStatementCreator. However, the latter is
 * hardly ever necessary, as most custom callback actions will perform updates
 * in which case a standard PreparedStatement is fine. Custom actions will
 * always set parameter values themselves, so that PreparedStatementCreator
 * capability is not needed either.
 *
 * @author egan
 * @email egzosn@gmail.com
 * @date 2017/11/29
 */
public class EntityPreparedStatementCallback<T, P> implements  org.springframework.jdbc.core.PreparedStatementCallback<T>{
    private static Logger logger = LoggerFactory.getLogger(PreparedStatementCreator.class);

    /**
     * Parameter collection
     */
    private final Object[] args;
    /**
     * entity Descriptor
     */
    private final SingleTableEntityPersister<P> entityPersister;
    /**
     * ORM Operating object
     */
    private  P entity;
    /**
     * ORM Operating object list
     */
    private  Collection<P> entitys;
    /**
     * is Batch processing
     */
    private final boolean isBatch ;
    /**
     *  is Insert processing
     *  If it's insert,  return  {@link #id}
     *      else, return  {@link #rowCont}
     */
    private final  boolean isInsert;

    /**
     *  id
     */
    private T id;

    /**
     * Successful record number
     */
    private int rowCont = 0;
    /**
     *
     * @param entityPersister
     * @param entity
     */
    public EntityPreparedStatementCallback(SingleTableEntityPersister<P> entityPersister, P entity) {
        this(entityPersister, entity, true);
    }

    public EntityPreparedStatementCallback(SingleTableEntityPersister<P> entityPersister, Collection<P> entity) {
        this(entityPersister, entity, true);

    }

    public EntityPreparedStatementCallback(SingleTableEntityPersister<P> entityPersister, P entity, boolean isInsert) {
        this.entityPersister = entityPersister;
        this.entitys = entitys;
        isBatch = false;
        this.isInsert = isInsert;
        if (isInsert){
            if (!entityPersister.getIdField().autoGeneratedKeys()){
                id = (T)this.entityPersister.idGenerated(entity, null);
            }
            this.args = entityPersister.getInsertFieldValues(entity);
        }else {
            this.args = entityPersister.getUpdateByRowIdKeyFieldValues(entity);
        }

    }

    public EntityPreparedStatementCallback(SingleTableEntityPersister<P> entityPersister, Collection<P> entitys, boolean isInsert) {
        this.entityPersister = entityPersister;
        this.entitys = entitys;
        isBatch = true;
        this.isInsert = isInsert;
        if (isInsert){
            if (!entityPersister.getIdField().autoGeneratedKeys()){
                id = (T)this.entityPersister.idGenerated(entitys, null);
            }
            this.args = entityPersister.getInsertFieldValues(entitys);
        }else {
            this.args = entityPersister.getUpdateByRowIdKeyFieldValues(entity);
        }




    }


    /**
     * Gets called by {@code JdbcTemplate.execute} with an active JDBC
     * PreparedStatement. Does not need to care about closing the Statement
     * or the Connection, or about handling transactions: this will all be
     * handled by Spring's JdbcTemplate.
     * <p><b>NOTE:</b> Any ResultSets opened should be closed in finally blocks
     * within the callback implementation. Spring will close the Statement
     * object after the callback returned, but this does not necessarily imply
     * that the ResultSet resources will be closed: the Statement objects might
     * get pooled by the connection pool, with {@code close} calls only
     * returning the object to the pool but not physically closing the resources.
     * <p>If called without a thread-bound JDBC transaction (initiated by
     * DataSourceTransactionManager), the code will simply get executed on the
     * JDBC connection with its transactional semantics. If JdbcTemplate is
     * configured to use a JTA-aware DataSource, the JDBC connection and thus
     * the callback code will be transactional if a JTA transaction is active.
     * <p>Allows for returning a result object created within the callback, i.e.
     * a domain object or a collection of domain objects. Note that there's
     * special support for single step actions: see JdbcTemplate.queryForObject etc.
     * A thrown RuntimeException is treated as application exception, it gets
     * propagated to the caller of the template.
     *
     * @param ps active JDBC PreparedStatement
     *
     * @return a result object, or {@code null} if none
     * @throws SQLException        if thrown by a JDBC method, to be auto-converted
     *                             to a DataAccessException by a SQLExceptionTranslator
     * @throws DataAccessException in case of custom exceptions
     * @see JdbcTemplate#queryForObject(String, Object[], Class)
     * @see JdbcTemplate#queryForList(String, Object[])
     */
    @Override
    public T doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {

        SQLTools.fillStatement(ps, args);
        if (isBatch   && (rowCont = args.length) > 0){
            int success = ps.executeBatch().length ;
            if (success > 0 && success < rowCont){
                logger.warn("The number of successful {}, now successful {} ", rowCont, success );
            }
            rowCont = success;
        }else {
            rowCont = ps.executeUpdate();
        }

        if (rowCont < 1) {
            throw new SQLException("sql:{} On failure.", ps.toString());
        }

        if (this.isInsert){
            if (entityPersister.getIdField().autoGeneratedKeys()){
                id = generated(ps);
            }
            return id;
        }

        return (T) (Object)rowCont;


    }

    /**
     * 生成id
     * @param ps 数据库操作
     * @return
     */
    private T generated(PreparedStatement ps) {

        if (!isBatch){
            id = (T) entityPersister.idGenerated(entity, ps);
        } else {
            id = (T) entityPersister.idGenerated(entitys, ps);
        }

        return id;
    }


    public int getRowCont() {
        return rowCont;
    }
}
